﻿using System;
using System.Collections.Specialized;
using System.Reflection;
using System.Text;
using SimpleServiceBus.EndpointManagement.Wcf.Contracts.Data.Analytics;
using SimpleServiceBus.EndpointMonitor.Web.Helpers.Flot.FlotOptions;

namespace SimpleServiceBus.EndpointMonitor.Web.Helpers.Flot
{
    public static class FlotHelper
    {
        private static readonly DateTime UnixInitDateTime = new DateTime(1970, 1, 1);

        public static string ToFlotJson<X, Y>(this DataSetContract<X, Y> dataset)
        {
            return dataset.ToFlotJson(null);
        }

        public static string ToFlotJson<X, Y>(this DataSetContract<X, Y> dataset, NameValueCollection seriesDefinitions)
        {
            if (dataset == null)
                return String.Empty;
            var sb = new StringBuilder("[");
            if (dataset.DataSeries != null)
            {
                for (int i = 0; i < dataset.DataSeries.Count; i++)
                {
                    if (i > 0)
                        sb.Append(",");
                    sb.Append(dataset.DataSeries[i].ToFlotJson(seriesDefinitions));
                }
            }
            sb.Append("]");

            string primarySeriesNames = ExtractPrimarySeriesNames(dataset).ToFlotJson();

            return "{ dataSet: " + sb + ", xAxisLabels: " + primarySeriesNames + "}";
        }

        

        private static string ToFlotJson<X, Y>(this DataSeriesContract<X, Y> series,
                                               NameValueCollection seriesDefinitions)
        {
            if (series == null)
                return String.Empty;

            var dataString = new StringBuilder("[");
            var captionString = new StringBuilder("[");
            var identifierString = new StringBuilder("[");

            bool returnCaptions = GetParameterValue<bool>(seriesDefinitions, "returnCaptions");
            bool returnIdentifiers = GetParameterValue<bool>(seriesDefinitions, "returnIdentifiers");
            bool pivot = GetParameterValue<bool>(seriesDefinitions, "pivot");

            int count = 0;
            if (series.DataPoints != null)
            {
                foreach (var point in series.DataPoints)
                {
                    if (count > 0)
                    {
                        dataString.Append(",");
                        if (returnCaptions) captionString.Append(",");
                        if (returnIdentifiers) identifierString.Append(",");
                    }

                    dataString.AppendFormat(GetJsonArrayTemplate(pivot, false), GetPointValue(point.X,returnIdentifiers,count), GetPointValue(point.Y,returnIdentifiers,count));
                    if (returnCaptions) captionString.AppendFormat(GetJsonArrayTemplate(pivot, true), point.X.Caption, point.Y.Caption);
                    if (returnIdentifiers)
                        identifierString.AppendFormat(GetJsonArrayTemplate(pivot, true), GetIdentifier(point.X), GetIdentifier(point.Y));
                    count++;
                }
            }

            dataString.Append("]");
            captionString.Append("]");
            identifierString.Append("]");

            string seriesJson = null;

            if (seriesDefinitions != null && series.Name != null)
            {
                seriesJson = seriesDefinitions[series.Name];
                //if the tuples used to create a series are multi-dimensional, then
                //the series name will be series1, series2, series n...
                //however, I'd like to allow the dev to have the option to specify series
                //properties based on just the first dimension, thus the code below
                if (String.IsNullOrEmpty(seriesJson) && series.Name.Contains(","))
                    seriesJson = seriesDefinitions[series.Name.Split(',')[0]];

                if (!String.IsNullOrEmpty(seriesJson))
                    seriesJson = seriesJson
                        .Replace("#seriesName#", series.Name)
                        .Replace("#seriesData#", dataString.ToString())
                        .Replace("#captionData#", captionString.ToString())
                        .Replace("#identifierData#",identifierString.ToString());
            }

            if (seriesJson == null)
            {
                var seriesOptions = new FlotSeriesOptions
                                        {
                                            Label = series.Name,
                                            Data = dataString.ToString(),
                                            Captions = captionString.ToString(),
                                            Identifiers = identifierString.ToString()
                                        };
                seriesJson = seriesOptions.ToFlotJson();
            }

            return seriesJson;
        }

        private static string GetJsonArrayTemplate(bool pivot, bool addQuotes)
        {
            //"[{0},{1}]"
            StringBuilder arrayBuilder = new StringBuilder();

            arrayBuilder.Append("[");

            if (addQuotes)
                arrayBuilder.Append("\"");

            if (pivot)
                arrayBuilder.Append("{1}");
            else
                arrayBuilder.Append("{0}");

            if (addQuotes)
                arrayBuilder.Append("\"");

            arrayBuilder.Append(",");

            if (addQuotes)
                arrayBuilder.Append("\"");

            if (pivot)
                arrayBuilder.Append("{0}");
            else
                arrayBuilder.Append("{1}");

            if (addQuotes)
                arrayBuilder.Append("\"");

            arrayBuilder.Append("]");

            return arrayBuilder.ToString();
        }

        private static string GetPointValue<T>(DataValueContract<T> val,bool returnIdentifiers,int index)
        {
            if (returnIdentifiers && IsIdentifier<T>())
                return index.ToString();
            return EnsureType(val.Value);

        }

        private static string GetIdentifier<T>(DataValueContract<T> val)
        {
            if (IsIdentifier<T>() == false)
                return String.Empty;

            if (Equals(val.Value, default(T)))
                throw new ArgumentNullException("val");
            
            return val.Value.ToString();
        }

        private static bool IsIdentifier<T>()
        {
            return !(typeof (ValueType).IsAssignableFrom(typeof (T)));
        }

        private static T GetParameterValue<T>(NameValueCollection seriesDefinitions, string key)
        {
            if (seriesDefinitions == null)
                return default(T);

            string val = seriesDefinitions[key];
            if (val == null)
                return default(T);
            return (T)Convert.ChangeType(val, typeof (T));

        }

        private static string EnsureType(object value)
        {
            if (value == null)
                return "0";
            return value is DateTime
                       ? Convert.ToInt64(((DateTime) value).Subtract(UnixInitDateTime).TotalMilliseconds).ToString()
                       : value.ToString();
        }

        private static string[] ExtractPrimarySeriesNames<X, Y>(DataSetContract<X, Y> dataset)
        {
            if (dataset.DataSeries == null || dataset.DataSeries.Count < 1 || dataset.DataSeries[0].DataPoints == null)
                return new string[0];

            var names = new string[dataset.DataSeries[0].DataPoints.Count];
            for (int i = 0; i < dataset.DataSeries[0].DataPoints.Count; i++)
            {
                names[i] = dataset.DataSeries[0].DataPoints[i].X.Caption;

            }
            return names;
        }

        public static string ToFlotJson(this object target)
        {
            if (target == null)
                return String.Empty;
            if (target is IFlotJsonSerializable)
                return ((IFlotJsonSerializable) target).ToJson();
            if (target is ValueType)
            {
                if (target is Enum)
                    return "\"" + MakeFirstCharLowerCase(target.ToString()) + "\"";
                return MakeFirstCharLowerCase(target.ToString());
            }
            if (target is string)
                return (string) target;
            if (target is string[])
                return "[" + ((string[]) target).Join(",", "\"{0}\"") + "]";


            var propList = new StringBuilder();

            PropertyInfo[] props =
                target.GetType().GetProperties(BindingFlags.Public | BindingFlags.FlattenHierarchy |
                                               BindingFlags.Instance);
            if (props != null && props.Length > 0)
            {
                foreach (PropertyInfo prop in props)
                {
                    object val = prop.GetValue(target, new object[0]);
                    if (val == null)
                        continue;

                    string valString;

                    if (prop.PropertyType == typeof(string))
                    {
                        valString = "\"{0}\"".FormatWith(val.ToFlotJson());
                    }
                    else
                    {
                        valString = val.ToFlotJson();
                    }

                    if (valString != null)
                    {
                        if (propList.Length > 0)
                            propList.Append(", ");

                        propList.Append("{0}: ".FormatWith(MakeFirstCharLowerCase(prop.Name)));
                        propList.Append(valString);
                    }
                }
            }

            if (propList.Length > 0)
            {
                return "{" + propList + "}";
            }
            return null;
        }

        public static string MakeFirstCharLowerCase(string target)
        {
            if (String.IsNullOrEmpty(target))
                return String.Empty;

            if (target.Length == 1)
                return target.ToLower();
            return Char.ToLower(target[0]) + target.Substring(1);
        }

        public static T Ensure<T>(ref T field) where T : class, new()
        {
            if (field == null)
                field = new T();
            return field;
        }
    }
}